// scalaxx - Scala XML eXtensions
// (c) 2009-2010, Normen Müller, normen.mueller@gmail.com
// $Id: Expressions.scala 78 2010-05-11 21:09:38Z normen.mueller $
package scalaxx.path

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
sealed abstract class Expression {
  def flush(attrs: List[Expression]) = (new StringBuilder /: attrs)((b,a) => b append "[" append a append "]") 
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
sealed abstract class LocationPath extends Expression {
  /* child axis
   */
  lazy val children = this match {
    case Path(s) => Path(s ::: List(Axis("child", All, Nil)))
    case Root(s) => Root(s ::: List(Axis("child", All, Nil)))
    case _       => this
  }
  
  def childAt(pos: Int) = this match {
    case Path(s) => Path(s ::: List(Axis("child", All, List(NVal(pos.toDouble)))))
    case Root(s) => Root(s ::: List(Axis("child", All, List(NVal(pos.toDouble)))))
    case _       => this
  }  
  
  /* parent axis
   */
  lazy val parent = this match {
    case Path(s) => Path(s slice (0, s.length - 1))
    case Root(s) => Root(s slice (0, s.length - 1))
    case _       => this
  }
  
  /* attribute axis
   */
  lazy val attributes = this match {
    case Path(s) => Path(s ::: List(Axis("attribute", All, Nil)))
    case Root(s) => Root(s ::: List(Axis("attribute", All, Nil)))
    case _       => this
  }
  
  // Note: https://lampsvn.epfl.ch/trac/scala/ticket/2735
  // The attribute order is not preserved :( (which fine regarding the XML Spec)
  // Also cf. scalaxx.MetaDataExtras#positionOf
  def attributeAt(position: Int) = this match {
    case Path(s) => Path(s ::: List(Axis("attribute", All, List(NVal(position.toDouble)))))
    case Root(s) => Root(s ::: List(Axis("attribute", All, List(NVal(position.toDouble)))))
    case _       => this
  }
  
  def attribute(name: String) = this match {
    case Path(s) => Path(s ::: List(Axis("attribute", NCName(name), Nil)))
    case Root(s) => Root(s ::: List(Axis("attribute", NCName(name), Nil)))
    case _       => this
  }  
  /* text node
   */
  lazy val text = this match {
    case Path(s) => Path(s ::: List(Axis("child", Type("text"), Nil)))
    case Root(s) => Root(s ::: List(Axis("child", Type("text"), Nil)))
    case _       => this
  }

  def ++ = this match {
    case Path(s) => Path(inc(s))
    case Root(s) => Root(inc(s))
    case _       => this
  }

  private def inc(s: List[Expression]) = 
    if(s isEmpty) s
    else s.last match {
      case Axis("child", All, List(NVal(p)))     => (s slice (0, s.length - 1)) ++ List(Axis("child", All, List(NVal(p + 1))))
      case Axis("attribute", All, List(NVal(p))) => (s slice (0, s.length - 1)) ++ List(Axis("attribute", All, List(NVal(p + 1))))
      case _                                     => s
    }

  def -- = this match {
    case Path(s) => Path(dec(s))
    case Root(s) => Root(dec(s))
    case _       => this
  }

  private def dec(s: List[Expression]) = 
    if(s isEmpty) s
    else s.last match {
      case Axis("child", All, List(NVal(p)))     => (s slice (0, s.length - 1)) ++ List(Axis("child", All, List(NVal({if(p > 1) p - 1 else p}))))
      case Axis("attribute", All, List(NVal(p))) => (s slice (0, s.length - 1)) ++ List(Axis("attribute", All, List(NVal({if(p > 1) p - 1 else p}))))
      case _                                     => s
    }
}

/** @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case object Root extends Root(Nil)

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Root(steps: List[Expression]) extends LocationPath { override def toString = steps mkString ("/", "/", "") }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Path(steps: List[Expression]) extends LocationPath { override def toString = steps mkString "/" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
trait NodeTest

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Filter(expr: Expression, attrs: List[Expression]) extends Expression { override def toString = expr.toString + flush(attrs) }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Axis(val name: String, val ident: NodeTest, val attrs: List[Expression]) extends Expression { override def toString = name + "::" + ident + flush(attrs) }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case object All extends Expression with NodeTest { override def toString = "*" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Fun(name: String, args: List[Expression]) extends Expression { override def toString = name + (args mkString ("(", ", ", ")")) }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Type(override val name: String) extends Fun(name, Nil) with NodeTest

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class NCName(local: String) extends Expression with NodeTest { override def toString = local }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class QName(prefix: String, local: String) extends Expression with NodeTest { override def toString = prefix+":"+local }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class $(v: String) extends Expression { override def toString = "$" + v }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
sealed abstract class Val extends Expression

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class NVal(v: Double) extends Val  { override def toString = v.toString}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class TVal(v: String) extends Val { override def toString = v mkString ("\"", "", "\"") }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
sealed abstract class Unary(right: Expression) extends Expression {
  val symbol: String
  
  override def toString = symbol + " " + right
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Negative(right: Expression) extends Unary(right) { val symbol = "-" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
sealed abstract class Binary(left: Expression, right: Expression) extends Expression {
  val symbol: String
  
  override def toString = "("+left+" "+symbol+" "+right+")"
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Union(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "|" }

/** @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Or(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "or" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class And(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "and" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Equal(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "=" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Unequal(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "!=" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class LTE(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "<=" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class LT(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "<" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class GTE(left: Expression, right: Expression) extends Binary(left, right) { val symbol = ">=" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class GT(left: Expression, right: Expression) extends Binary(left, right) { val symbol = ">" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Plus(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "+" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Minus(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "-" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Mult(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "*" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Div(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "div" }

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.3
 */
case class Mod(left: Expression, right: Expression) extends Binary(left, right) { val symbol = "mod" }
